﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace HttpRangeTest
{
    class Program
    {
        static void Main(string[] args)
        {
            string url = "http://demo.juketool.com/part.csv";
            //string desFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "part.csv");
            //Download(url, desFile);

            //string url2 = "http://demo.juketool.com/normal.csv";
            string desFile2 = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "normal.csv");
            Task.Factory.StartNew(() => Download2(url, desFile2));

            Console.ReadLine();
        }

        /// <summary>
        /// 单线程断点下载
        /// </summary>
        /// <param name="url"></param>
        /// <param name="desFile"></param>
        /// <returns></returns>
        static bool Download(string url, string desFile)
        {
            long length = GetHttpLength(url);
            Console.WriteLine($"文件大小:{length}kb...");

            long position = 0L;
            FileStream fileStream = null;

            // 计算position
            if (File.Exists(desFile))
            {
                fileStream = File.OpenWrite(desFile);
                position = fileStream.Length;

                if (position == length)
                {
                    Console.WriteLine("下载完成...");
                    return true;
                }

                fileStream.Seek(position, SeekOrigin.Current);
            }
            else
            {
                fileStream = new FileStream(desFile, FileMode.Create);
                position = 0;
            }

            // 开始下载
            var request = (HttpWebRequest)HttpWebRequest.Create(url);
            if (position > 0)
            {
                request.AddRange(position);
            }

            var stream = request.GetResponse().GetResponseStream();
            var buffer = new byte[1024];
            int readed = 0;
            while ((readed = stream.Read(buffer, 0, 1024)) > 0)
            {
                fileStream.Write(buffer, 0, readed);
                fileStream.Flush();
            }

            stream.Close();
            fileStream.Close();

            Console.WriteLine("下载完成...");

            return true;
        }

        /// <summary>
        /// 多线程分块下载
        /// </summary>
        /// <param name="url"></param>
        /// <param name="desFile"></param>
        /// <returns></returns>
        static async Task<bool> Download2(string url, string desFile)
        {
            long length = GetHttpLength(url);
            Console.WriteLine($"文件大小:{length}kb...");

            string directoryName = Path.GetDirectoryName(desFile);
            string fileName = Path.GetFileNameWithoutExtension(desFile);
            string ext = Path.GetExtension(desFile);

            // 按每块20M分块
            long size = 20 * 1024 * 1024;
            int amount = (int)Math.Ceiling(length / size * 1.0);

            var tasks = new List<Task>(amount);
            var tempFiles = new List<string>(amount);

            for (int i = 0; i < amount; i++)
            {
                long from = i * size;
                long to = i * size + size;

                if (i == amount - 1)
                {
                    to = length;  // last part
                }

                string temp = Path.Combine(directoryName, $"{fileName}_{i}.download");
                tempFiles.Add(temp);
                tasks.Add(Task.Run(() =>
                {
                    PartDown(url, from, to, temp);
                }));
            }

            // 下载子文件
            await Task.WhenAll(tasks);

            // 合并文件
            using (FileStream fs = new FileStream(desFile, FileMode.Create))
            {
                tempFiles.ForEach(temp =>
                {
                    using (FileStream tempFs = new FileStream(temp, FileMode.Open))
                    {
                        byte[] buffer = new byte[1024];
                        int readed = 0;
                        while((readed = tempFs.Read(buffer, 0, 1024)) > 0)
                        {
                            if (readed < 1024 && buffer[readed - 1] == 0)
                            {
                                fs.Write(buffer, 0, readed - 1); // 去除每个子文件的最后一个\0字节
                            }
                            else
                            {
                                fs.Write(buffer, 0, 1024);
                            }
                        }
                    }
                });
            }

            // 删除子文件
            //tempFiles.ForEach(temp => 
            //{
            //    File.Delete(temp);
            //});

            Console.WriteLine("文件合并完成...");
            return true;
        }

        static void PartDown(string url, long from, long to, string tempFile)
        {
            FileStream fileStream = null;

            if (File.Exists(tempFile))
            {
                fileStream = File.OpenWrite(tempFile);
                long position = fileStream.Length;

                if (position == (to - from) + 1)   // 去除\0一个字节
                {
                    Console.WriteLine($"{tempFile}完成...");
                    return;
                }

                from = from + position;
                fileStream.Seek(position, SeekOrigin.Current);
            }
            else
            {
                fileStream = new FileStream(tempFile, FileMode.Create);
            }

            var request = (HttpWebRequest)HttpWebRequest.Create(url);
            request.AddRange(from, to);

            var stream = request.GetResponse().GetResponseStream();
            var buffer = new byte[1024];
            int readed = 0;
            while ((readed = stream.Read(buffer, 0, 1024)) > 0)
            {
                fileStream.Write(buffer, 0, readed);
                fileStream.Flush();
            }

            Console.WriteLine($"{tempFile}完成...");

            stream.Close();
            fileStream.Close();
        }

        /// <summary>
        /// 获取响应报文主体长度
        /// </summary>
        /// <param name="url">url</param>
        /// <returns></returns>
        static long GetHttpLength(string url)
        {
            try
            {
                long length = 0L;

                var req = (HttpWebRequest)WebRequest.CreateDefault(new Uri(url));
                req.Method = "HEAD";
                req.Timeout = 5000;

                var resp = (HttpWebResponse)req.GetResponse();
                if (resp.StatusCode == HttpStatusCode.OK)
                {
                    length = resp.ContentLength;
                }

                resp.Close();
                return length;
            }
            catch (Exception ex)
            {
                return 0L;
            }
        }
    }
}
